Companion notebook for the Introduction to dplyr slides

Motivation

Prerequisites: Install the nycflights13 and tidyverse packages

library(tidyverse)
library(nycflights13)

Data from nycflights13

This dataset contains flights departing New York City (NYC) in 2013. It contains all 336,776 flights that departed from NYC in 2013.

The data comes from the US Bureau of Transportation Statistics, and is documented in ?flights

The tidyverse

The tidyverse is an opinionated collection of R packages designed for data science. All packages share an underlying design philosophy, grammar, and data structures.

Definition of tidy data: (see paper

Core tidyverse packages

Package Name Usage
ggplot2 data visualization
dplyr data manipulation
tidyr tidy up data
readr data import
purrr functional programming
tibble dataframes reimagined
stringr working with strings
forcats working with factors

dplyr basics

  1. Pick observations by their values: filter()

  2. Reorder the rows: arrange()

  3. Pick variables by their names: select()

  4. Create new variables with functions of existing variables: mutate()

  5. Collapse many values down to a single summary: summarize()

  6. Operate on a group-by-group basis: group_by()

Filtering rows

filter() allows you to subset observations based on their values. For example, we can select all flights on January 1st with:

filter(flights, month == 1, day == 1)

Comparisons

dplyr functions never modify their inputs, so if you want to save the result, you will need to use the assignment operator, <-

jan1 <- filter(flights, month == 1, day == 1)
jan1

To use filtering effectively, you have to know how to select the observations that you want using comparison operators:

>, >=, <, <=,

!= (not equal), and

== (equal).

Logical operators

]

Flights in November OR December

The following code finds all flights that departed in November OR December:

filter(flights, month == 11 | month == 12)

A useful short-hand is x %in% y. This will select every row where x is one of the values in y. We could use it to rewrite the code above:

nov_dec <- filter(flights, month %in% c(11, 12))
nov_dec

Arrange rows with arrange()

arrange() works similarly to filter() except that instead of selecting rows, it changes their order.

It takes a data frame and a set of column names to order by.

arrange(flights, year, month, day)

Use desc() to re-order by a column in descending order:

arrange(flights, desc(arr_delay))

Select columns with select()

select() allows you to rapidly zoom in on a useful subset using the names of the variables.

# Select columns by name
select(flights, year, month, day)

There are a number of helper functions you can use within select():

starts_with("abc"): matches names that begin with "abc".

ends_with("xyz"): matches names that end with "xyz".

contains("ijk"): matches names that contain "ijk".

Add new variables with mutate()

To add new columns that are functions of existing columns. That is the job of mutate(). mutate() always adds new columns at the end of your dataset.

# create a smaller dataset with less columns
flights_sml <- select(flights,
  year:day, 
  ends_with("delay"), 
  distance, 
  air_time
)
flights_sml

Example: using mutate()

mutate(flights_sml,
       gain = arr_delay - dep_delay,
       speed = distance / air_time * 60
)
flights_sml

Note that you can refer to columns that you have just created:

mutate(flights_sml,
       gain = arr_delay - dep_delay,
       hours = air_time/60,
       gain_per_hour = gain/hours
)

Grouped summaries with summarise()

summarise() collapses a data frame to a single row:

summarise(flights, delay = mean(dep_delay, na.rm = TRUE))

The summarise() function is useful when we pair it with group_by().

This way, the analysis can be done for individual groups.

The pipe operator

The pipe operator is given by %>% (from the magrittr package)

The pipe sends the output of the LHS function to the first argument of the RHS function. For example:

# pipe example
sum(1:8) %>%
  sqrt() %>% 
  log()
[1] 1.791759

is equivalent to

# the log of the square root
# of the sum of the elements 
# of the vector [1 ... 8]
log(sqrt(sum(1:8)))
[1] 1.791759

Example for data exploration

Imagine that we want to explore the relationship between the distance and average delay for each location.

There are three steps to prepare this data:

  1. Group flights by destination.

  2. Summarize to compute distance, average delay, and number of flights.

  3. Filter to remove noisy points and Honolulu airport, which is almost twice as far away as the next closest airport.

Power of the pipe %>% operator

# create dataframe of "delays"
delays <- flights %>%
  group_by(dest) %>%
  summarise(
    count = n(),
    dist = mean(distance, na.rm = TRUE),
    delay = mean(arr_delay, na.rm = TRUE)
  ) %>%
  filter(count > 20, dest != "HNL")
delays

If you use RStudio, you can type the pipe with Ctrl + Shift + M if you have a PC or Cmd + Shift + M if you have a Mac.

You can read it as a series of imperative statements: group, then summarize, then filter. A good way to pronounce %>% when reading code is “then”.

The n() function is implemented specifically for each data source and can be used from within summarize(), mutate() and filter(). It returns the number of observations in the current group.

Transformations

Why na.rm = TRUE ? Aggregation functions obey the usual rule of missing values: if there is any missing value in the input, the output will be a missing value!

flights %>%
  group_by(year, month, day) %>%
  summarize(mean = mean(dep_delay, na.rm = TRUE))
`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
LS0tDQp0aXRsZSA6ICJJbnRyb2R1Y3Rpb24gdG8gYGRwbHlyYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KPiBDb21wYW5pb24gbm90ZWJvb2sgZm9yIHRoZSBbSW50cm9kdWN0aW9uIHRvIGBkcGx5cmAgc2xpZGVzXShodHRwczovL3d3dy5yZWlzYW5hci5jb20vc2xpZGVzL2ludHJvZHVjaW5nLWRwbHlyIzEpIA0KDQojIE1vdGl2YXRpb24NCg0KDQotIE9mdGVuIHlvdSB3aWxsIG5lZWQgdG8gY3JlYXRlIHNvbWUgKm5ldyB2YXJpYWJsZXMqIG9yICpzdW1tYXJpZXMqLCBvciBtYXliZSB5b3UganVzdCB3YW50IHRvICpyZW5hbWUgKnRoZSB2YXJpYWJsZXMgb3IgKnJlb3JkZXIqIHRoZSBvYnNlcnZhdGlvbnMgdG8gbWFrZSB0aGUgZGF0YSBhIGxpdHRsZSBlYXNpZXIgdG8gd29yayB3aXRoLg0KDQotIFdlIHdpbGwgZm9jdXMgb24gaG93IHRvIHVzZSB0aGUgYGRwbHlyYCBwYWNrYWdlLCBhbm90aGVyIGNvcmUgbWVtYmVyIG9mIHRoZSBgdGlkeXZlcnNlYC4gDQoNCioqUHJlcmVxdWlzaXRlczoqKiBJbnN0YWxsIHRoZSBgbnljZmxpZ2h0czEzYCBhbmQgYHRpZHl2ZXJzZWAgcGFja2FnZXMNCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSxlY2hvPVRSVUV9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobnljZmxpZ2h0czEzKQ0KYGBgDQoNCg0KIyBEYXRhIGZyb20gYG55Y2ZsaWdodHMxM2AgDQoNCg0KVGhpcyBkYXRhc2V0IGNvbnRhaW5zIGZsaWdodHMgZGVwYXJ0aW5nIE5ldyBZb3JrIENpdHkgKE5ZQykgaW4gMjAxMy4gSXQgY29udGFpbnMgYWxsIDMzNiw3NzYgZmxpZ2h0cyB0aGF0IGRlcGFydGVkIGZyb20gTllDIGluIDIwMTMuIA0KDQpgYGB7ciwgZWNobyA9IEZ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3JlaXNhbmFyL2ZpZ3MvbWFzdGVyL2ZsaWdodHMxMy5wbmciKQ0KYGBgDQoNCg0KVGhlIGRhdGEgY29tZXMgZnJvbSB0aGUgW1VTIEJ1cmVhdSBvZiBUcmFuc3BvcnRhdGlvbiBTdGF0aXN0aWNzXShodHRwczovL3d3dy50cmFuc3RhdHMuYnRzLmdvdi9EYXRhYmFzZUluZm8uYXNwP0RCX0lEPTEyMCZMaW5rPTApLCBhbmQgaXMgZG9jdW1lbnRlZCBpbiBgP2ZsaWdodHNgDQoNCg0KDQojIFRoZSBgdGlkeXZlcnNlYA0KDQoNClRoZSBgdGlkeXZlcnNlYCBpcyBhbiBvcGluaW9uYXRlZCBbY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL3BhY2thZ2VzLykgZGVzaWduZWQgZm9yIGRhdGEgc2NpZW5jZS4gQWxsIHBhY2thZ2VzIHNoYXJlIGFuIHVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMuDQoNCg0KRGVmaW5pdGlvbiBvZiAqKnRpZHkgZGF0YSoqOiAoc2VlIFtwYXBlcl0oKGh0dHBzOi8vdml0YS5oYWQuY28ubnovcGFwZXJzL3RpZHktZGF0YS5wZGYpIGJ5IEhhZGxleSBXaWNraGFtKQ0KDQotIEVhY2ggdmFyaWFibGUgaXMgYSBjb2x1bW4NCg0KLSBFYWNoIG9ic2VydmF0aW9uIGlzIGEgcm93DQoNCi0gRWFjaCB0eXBlIG9mIG9ic2VydmF0aW9uYWwgdW5pdCBpcyBhIHRhYmxlDQoNCg0KIyBDb3JlIGB0aWR5dmVyc2VgIHBhY2thZ2VzDQoNCg0KfCBQYWNrYWdlIE5hbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgIFVzYWdlICAgICAgICAgICAgICAgICB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgW2BnZ3Bsb3QyYF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSB8IGRhdGEgdmlzdWFsaXphdGlvbiAgICAgfA0KfCBbYGRwbHlyYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgICAgIHwgZGF0YSBtYW5pcHVsYXRpb24gICAgICB8DQp8IFtgdGlkeXJgXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvKSAgICAgfCB0aWR5IHVwIGRhdGEgICAgICAgICAgIHwNCnwgW2ByZWFkcmBdKGh0dHBzOi8vcmVhZHIudGlkeXZlcnNlLm9yZy8pICAgICB8IGRhdGEgaW1wb3J0ICAgICAgICAgICAgfA0KfCBbYHB1cnJyYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnLykgICAgIHwgZnVuY3Rpb25hbCBwcm9ncmFtbWluZyB8DQp8IFtgdGliYmxlYF0oaHR0cHM6Ly90aWJibGUudGlkeXZlcnNlLm9yZy8pICAgfCBkYXRhZnJhbWVzIHJlaW1hZ2luZWQgIHwNCnwgW2BzdHJpbmdyYF0oaHR0cHM6Ly9zdHJpbmdyLnRpZHl2ZXJzZS5vcmcvKSB8IHdvcmtpbmcgd2l0aCBzdHJpbmdzICAgfA0KfCBbYGZvcmNhdHNgXShodHRwczovL2ZvcmNhdHMudGlkeXZlcnNlLm9yZy8pfCAgd29ya2luZyB3aXRoIGZhY3RvcnMgICB8DQoNCg0KDQpgYGB7ciwgb3V0LndpZHRoID0gIjEwMCUiLCBmaWcuYWxpZ249J2NlbnRlcicsIGVjaG8gPSBGfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZWlzYW5hci9maWdzL21hc3Rlci90aWR5dmVyc2UtcGFja2FnZXMucG5nIikNCmBgYA0KDQoNCg0KDQojIGBkcGx5cmAgYmFzaWNzDQoNCg0KYGBge3IsIGVjaG8gPSBGLCBmaWcuc2hvdz0naG9sZCcsIG91dC5oZWlnaHQ9IjI1JSIsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKA0KICBjKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmVpc2FuYXIvZmlncy9tYXN0ZXIvZHBseXItbmV3LW9sZC5wbmciKQ0KKQ0KYGBgDQoNCg0KMS4gKipQaWNrIG9ic2VydmF0aW9ucyoqIGJ5IHRoZWlyIHZhbHVlczogYGZpbHRlcigpYA0KDQoyLiAqKlJlb3JkZXIqKiB0aGUgcm93czogYGFycmFuZ2UoKWANCg0KMy4gKipQaWNrIHZhcmlhYmxlcyoqIGJ5IHRoZWlyIG5hbWVzOiBgc2VsZWN0KClgDQoNCjQuICoqQ3JlYXRlIG5ldyB2YXJpYWJsZXMqKiB3aXRoIGZ1bmN0aW9ucyBvZiBleGlzdGluZyB2YXJpYWJsZXM6IGBtdXRhdGUoKWANCg0KNS4gKipDb2xsYXBzZSoqIG1hbnkgdmFsdWVzIGRvd24gdG8gYSBzaW5nbGUgc3VtbWFyeTogYHN1bW1hcml6ZSgpYA0KDQo2LiBPcGVyYXRlIG9uIGEgKipncm91cC1ieS1ncm91cCoqIGJhc2lzOiBgZ3JvdXBfYnkoKWANCg0KDQoNCg0KIyBGaWx0ZXJpbmcgX3Jvd3NfDQoNCg0KYGZpbHRlcigpYCBhbGxvd3MgeW91IHRvIHN1YnNldCBvYnNlcnZhdGlvbnMgYmFzZWQgb24gdGhlaXIgdmFsdWVzLiAgRm9yIGV4YW1wbGUsIHdlIGNhbiBzZWxlY3QgYWxsIGZsaWdodHMgb24gSmFudWFyeSAxc3Qgd2l0aDoNCg0KDQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQ0KYGBgDQoNCg0KDQoNCiMgQ29tcGFyaXNvbnMNCg0KYGRwbHlyYCBmdW5jdGlvbnMgbmV2ZXIgbW9kaWZ5IHRoZWlyIGlucHV0cywgc28gaWYgeW91IHdhbnQgdG8gc2F2ZSB0aGUgcmVzdWx0LCB5b3Ugd2lsbCBuZWVkIHRvIHVzZSB0aGUgYXNzaWdubWVudCBvcGVyYXRvciwgYDwtYA0KDQpgYGB7cn0NCmphbjEgPC0gZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQ0KamFuMQ0KYGBgDQoNCg0KDQpUbyB1c2UgZmlsdGVyaW5nIGVmZmVjdGl2ZWx5LCB5b3UgaGF2ZSB0byBrbm93IGhvdyB0byBzZWxlY3QgdGhlIG9ic2VydmF0aW9ucyB0aGF0IHlvdSB3YW50IHVzaW5nIGNvbXBhcmlzb24gb3BlcmF0b3JzOiANCg0KYD5gLCBgPj1gLCBgPGAsIGA8PWAsIA0KDQpgIT1gIChub3QgZXF1YWwpLCBhbmQgDQoNCmA9PWAgKGVxdWFsKS4NCg0KDQoqKkxvZ2ljYWwgb3BlcmF0b3JzKioNCg0KYGBge3IsIG91dC53aWR0aCA9ICI2MDBweCIsIGZpZy5hbGlnbj0nY2VudGVyJywgZWNobyA9IEZ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cDovL3I0ZHMuaGFkLmNvLm56L2RpYWdyYW1zL3RyYW5zZm9ybS1sb2dpY2FsLnBuZyIpDQpgYGANCl0NCg0KDQojIEZsaWdodHMgaW4gTm92ZW1iZXIgX09SXyBEZWNlbWJlcg0KDQpUaGUgZm9sbG93aW5nIGNvZGUgZmluZHMgYWxsIGZsaWdodHMgdGhhdCBkZXBhcnRlZCBpbiBOb3ZlbWJlciBPUiBEZWNlbWJlcjoNCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgbW9udGggPT0gMTEgfCBtb250aCA9PSAxMikNCmBgYA0KDQpBIHVzZWZ1bCBzaG9ydC1oYW5kIGlzIGB4ICVpbiUgeWAuIFRoaXMgd2lsbCBzZWxlY3QgZXZlcnkgcm93IHdoZXJlIGB4YCBpcyBvbmUgb2YgdGhlIHZhbHVlcyBpbiBgeWAuIFdlIGNvdWxkIHVzZSBpdCB0byByZXdyaXRlIHRoZSBjb2RlIGFib3ZlOg0KDQpgYGB7cn0NCm5vdl9kZWMgPC0gZmlsdGVyKGZsaWdodHMsIG1vbnRoICVpbiUgYygxMSwgMTIpKQ0Kbm92X2RlYw0KYGBgDQoNCg0KDQojIEFycmFuZ2Ugcm93cyB3aXRoIGBhcnJhbmdlKClgDQoNCmBhcnJhbmdlKClgIHdvcmtzIHNpbWlsYXJseSB0byBgZmlsdGVyKClgIGV4Y2VwdCB0aGF0IGluc3RlYWQgb2Ygc2VsZWN0aW5nIHJvd3MsIGl0ICoqY2hhbmdlcyB0aGVpciBvcmRlcioqLiANCg0KSXQgdGFrZXMgYSBkYXRhIGZyYW1lIGFuZCBhIHNldCBvZiBjb2x1bW4gbmFtZXMgdG8gb3JkZXIgYnkuIA0KDQpgYGB7cn0NCmFycmFuZ2UoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCmBgYA0KDQoNClVzZSBgZGVzYygpYCB0byByZS1vcmRlciBieSBhIGNvbHVtbiBpbiBkZXNjZW5kaW5nIG9yZGVyOg0KDQpgYGB7cn0NCmFycmFuZ2UoZmxpZ2h0cywgZGVzYyhhcnJfZGVsYXkpKQ0KYGBgDQoNCiMgU2VsZWN0IGNvbHVtbnMgd2l0aCBgc2VsZWN0KClgDQoNCg0KYHNlbGVjdCgpYCBhbGxvd3MgeW91IHRvIHJhcGlkbHkgem9vbSBpbiBvbiBhIHVzZWZ1bCBzdWJzZXQgdXNpbmcgdGhlIG5hbWVzIG9mIHRoZSAqKnZhcmlhYmxlcyoqLg0KDQoNCg0KYGBge3IsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0NCiMgU2VsZWN0IGNvbHVtbnMgYnkgbmFtZQ0Kc2VsZWN0KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpDQpgYGANCg0KDQpUaGVyZSBhcmUgYSBudW1iZXIgb2YgaGVscGVyIGZ1bmN0aW9ucyB5b3UgY2FuIHVzZSB3aXRoaW4gYHNlbGVjdCgpYDoNCg0KYHN0YXJ0c193aXRoKCJhYmMiKWA6IG1hdGNoZXMgbmFtZXMgdGhhdCBiZWdpbiB3aXRoIGAiYWJjImAuDQoNCmBlbmRzX3dpdGgoInh5eiIpYDogbWF0Y2hlcyBuYW1lcyB0aGF0IGVuZCB3aXRoIGAieHl6ImAuDQoNCmBjb250YWlucygiaWprIilgOiBtYXRjaGVzIG5hbWVzIHRoYXQgY29udGFpbiBgImlqayJgLg0KDQoNCg0KIyBBZGQgbmV3IHZhcmlhYmxlcyB3aXRoIGBtdXRhdGUoKWANCg0KDQpUbyBfYWRkIG5ldyBjb2x1bW5zXyB0aGF0IGFyZSBmdW5jdGlvbnMgb2YgZXhpc3RpbmcgY29sdW1ucy4gVGhhdCBpcyB0aGUgam9iIG9mIGBtdXRhdGUoKWAuIGBtdXRhdGUoKWAgYWx3YXlzIGFkZHMgbmV3IGNvbHVtbnMgYXQgdGhlIGVuZCBvZiB5b3VyIGRhdGFzZXQuIA0KDQoNCmBgYHtyfQ0KIyBjcmVhdGUgYSBzbWFsbGVyIGRhdGFzZXQgd2l0aCBsZXNzIGNvbHVtbnMNCmZsaWdodHNfc21sIDwtIHNlbGVjdChmbGlnaHRzLA0KICB5ZWFyOmRheSwgDQogIGVuZHNfd2l0aCgiZGVsYXkiKSwgDQogIGRpc3RhbmNlLCANCiAgYWlyX3RpbWUNCikNCmZsaWdodHNfc21sDQpgYGANCg0KDQoNCiMgRXhhbXBsZTogdXNpbmcgYG11dGF0ZSgpYA0KDQoNCg0KYGBge3J9DQptdXRhdGUoZmxpZ2h0c19zbWwsDQogICAgICAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwNCiAgICAgICBzcGVlZCA9IGRpc3RhbmNlIC8gYWlyX3RpbWUgKiA2MA0KKQ0KZmxpZ2h0c19zbWwNCmBgYA0KDQpOb3RlIHRoYXQgeW91IGNhbiByZWZlciB0byBjb2x1bW5zIHRoYXQgeW91IGhhdmUganVzdCBjcmVhdGVkOg0KDQoNCmBgYHtyfQ0KbXV0YXRlKGZsaWdodHNfc21sLA0KICAgICAgIGdhaW4gPSBhcnJfZGVsYXkgLSBkZXBfZGVsYXksDQogICAgICAgaG91cnMgPSBhaXJfdGltZS82MCwNCiAgICAgICBnYWluX3Blcl9ob3VyID0gZ2Fpbi9ob3Vycw0KKQ0KYGBgDQoNCg0KDQojIEdyb3VwZWQgc3VtbWFyaWVzIHdpdGggYHN1bW1hcmlzZSgpYA0KDQpgc3VtbWFyaXNlKClgIF9jb2xsYXBzZXNfIGEgZGF0YSBmcmFtZSB0byBhIHNpbmdsZSByb3c6DQoNCmBgYHtyfQ0Kc3VtbWFyaXNlKGZsaWdodHMsIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KDQpUaGUgYHN1bW1hcmlzZSgpYCBmdW5jdGlvbiBpcyB1c2VmdWwgd2hlbiB3ZSBwYWlyIGl0IHdpdGggYGdyb3VwX2J5KClgLg0KDQpUaGlzIHdheSwgdGhlIGFuYWx5c2lzIGNhbiBiZSBkb25lIGZvciBpbmRpdmlkdWFsIGdyb3Vwcy4gDQoNCg0KDQojIFRoZSBwaXBlIG9wZXJhdG9yDQoNCg0KVGhlIHBpcGUgb3BlcmF0b3IgaXMgZ2l2ZW4gYnkgKipgJT4lYCoqIChmcm9tIHRoZSBgbWFncml0dHJgIFtwYWNrYWdlXShodHRwczovL21hZ3JpdHRyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3BpcGUuaHRtbCkpDQoNCg0KVGhlIHBpcGUgc2VuZHMgdGhlIG91dHB1dCBvZiB0aGUgTEhTIGZ1bmN0aW9uIHRvIHRoZSBmaXJzdCBhcmd1bWVudCBvZiB0aGUgUkhTIGZ1bmN0aW9uLg0KRm9yIGV4YW1wbGU6IA0KYGBge3J9DQojIHBpcGUgZXhhbXBsZQ0Kc3VtKDE6OCkgJT4lDQogIHNxcnQoKSAlPiUgDQogIGxvZygpDQpgYGANCg0KDQoNCmlzIGVxdWl2YWxlbnQgdG8gDQoNCmBgYHtyfQ0KIyB0aGUgbG9nIG9mIHRoZSBzcXVhcmUgcm9vdA0KIyBvZiB0aGUgc3VtIG9mIHRoZSBlbGVtZW50cyANCiMgb2YgdGhlIHZlY3RvciBbMSAuLi4gOF0NCmxvZyhzcXJ0KHN1bSgxOjgpKSkNCmBgYA0KDQoNCiMgRXhhbXBsZSBmb3IgZGF0YSBleHBsb3JhdGlvbg0KDQpJbWFnaW5lIHRoYXQgd2Ugd2FudCB0byBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGlzdGFuY2UgYW5kIGF2ZXJhZ2UgZGVsYXkgZm9yIGVhY2ggbG9jYXRpb24uIA0KDQoNCg0KVGhlcmUgYXJlIHRocmVlIHN0ZXBzIHRvIHByZXBhcmUgdGhpcyBkYXRhOg0KDQoxLiAqKkdyb3VwKiogZmxpZ2h0cyBieSBkZXN0aW5hdGlvbi4NCg0KMi4gKipTdW1tYXJpemUqKiB0byBjb21wdXRlIGRpc3RhbmNlLCBhdmVyYWdlIGRlbGF5LCBhbmQgbnVtYmVyIG9mIGZsaWdodHMuDQoNCjMuICoqRmlsdGVyKiogdG8gcmVtb3ZlIG5vaXN5IHBvaW50cyBhbmQgSG9ub2x1bHUgYWlycG9ydCwgd2hpY2ggaXMgYWxtb3N0IHR3aWNlIGFzIGZhciBhd2F5IGFzIHRoZSBuZXh0IGNsb3Nlc3QgYWlycG9ydC4NCg0KDQoNCg0KIyBQb3dlciBvZiB0aGUgcGlwZSBgJT4lYCBvcGVyYXRvciANCg0KDQpgYGB7cn0NCiMgY3JlYXRlIGRhdGFmcmFtZSBvZiAiZGVsYXlzIg0KZGVsYXlzIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY291bnQgPSBuKCksDQogICAgZGlzdCA9IG1lYW4oZGlzdGFuY2UsIG5hLnJtID0gVFJVRSksDQogICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKQ0KICApICU+JQ0KICBmaWx0ZXIoY291bnQgPiAyMCwgZGVzdCAhPSAiSE5MIikNCmRlbGF5cw0KYGBgDQoNCklmIHlvdSB1c2UgUlN0dWRpbywgeW91IGNhbiB0eXBlIHRoZSBwaXBlIHdpdGggQ3RybCArIFNoaWZ0ICsgTSBpZiB5b3UgaGF2ZSBhIFBDIG9yIENtZCArIFNoaWZ0ICsgTSBpZiB5b3UgaGF2ZSBhIE1hYy4NCg0KWW91IGNhbiByZWFkIGl0IGFzIGEgc2VyaWVzIG9mIGltcGVyYXRpdmUgc3RhdGVtZW50czogKipncm91cCoqLCB0aGVuICoqc3VtbWFyaXplKiosIHRoZW4gKipmaWx0ZXIqKi4gQSBnb29kIHdheSB0byBwcm9ub3VuY2UgYCU+JWAgd2hlbiByZWFkaW5nIGNvZGUgaXMgInRoZW4iLg0KDQpUaGUgYG4oKWAgZnVuY3Rpb24gaXMgaW1wbGVtZW50ZWQgc3BlY2lmaWNhbGx5IGZvciBlYWNoIGRhdGEgc291cmNlIGFuZCBjYW4gYmUgdXNlZCBmcm9tIHdpdGhpbiBgc3VtbWFyaXplKClgLCBgbXV0YXRlKClgIGFuZCBgZmlsdGVyKClgLiBJdCByZXR1cm5zIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIHRoZSBjdXJyZW50IGdyb3VwLg0KDQoNCg0KIyBUcmFuc2Zvcm1hdGlvbnMNCg0KDQoqKldoeSBgbmEucm0gPSBUUlVFYCA/KiogQWdncmVnYXRpb24gZnVuY3Rpb25zIG9iZXkgdGhlIHVzdWFsIHJ1bGUgb2YgbWlzc2luZyB2YWx1ZXM6IGlmIHRoZXJlIGlzIGFueSBtaXNzaW5nIHZhbHVlIGluIHRoZSBpbnB1dCwgdGhlIG91dHB1dCB3aWxsIGJlIGEgbWlzc2luZyB2YWx1ZSEgDQoNCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQoNCg0KDQo=